package com;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;

import de.dfki.util.xmlrpc.XmlRpc;

public class RPC implements RPCApi {
	
	/**
	 * Map: peer-name -> [cmd-name] -> time
	 * Also: see register_cmd and last_cmd
	 */
	private HashMap<String, HashMap<String, Long>> cmd_map;

	public String cmd(String cmd) {
		return p.parseCommand(cmd);
	}

	// Local peer
	private Peer p = null;

	public RPC(Peer p) {
		this.p = p;
		cmd_map = new HashMap<String, HashMap<String, Long>>();
	}
	
	public void ping() {
		// Received a ping from a Peer
		// Sleep randomly
		try {
			Random rand = new Random();
			int min = 0, max = 50;
			// nextInt is normally exclusive of the top value,
			// so add 1 to make it inclusive
			int randomNum = rand.nextInt(max - min + 1) + min;
		    Thread.sleep(randomNum);
		} catch(InterruptedException ex) {
		    Thread.currentThread().interrupt();
		}
	}
	
	// returns time since we received the command from name last time
	private Long last_cmd(String name, String cmd) {
		Long ret = 1000000L;  // artificially high number
		if(cmd_map.containsKey(name)) {
			HashMap<String, Long> cmap = cmd_map.get(name);
			if(cmap.containsKey(cmd)) {
				ret = System.currentTimeMillis() - cmap.get(cmd);
			}
		}
		return ret;
	}
	
	// registers the time for the cmd by the Peer 'name'
	private void register_cmd(String name, String cmd) {
		if(!cmd_map.containsKey(name)) {
			cmd_map.put(name, new HashMap<String, Long>());
		}
		
		HashMap<String, Long> m = cmd_map.get(name);
		m.put(cmd, System.currentTimeMillis());
		cmd_map.put(name, m);
	}

	public ArrayList<PeerData> query(int ttl, String name) {
		ArrayList<PeerData> resultList = new ArrayList<PeerData>();
		RPCApi remote_api;
		
		// TTL Mechanism, decrease once each time..
		--ttl;
		if (ttl == 0)
			return resultList;

		Long time_since_last_qry = last_cmd(name, "query");
		register_cmd(name, "query");
		// Reject queries if we've just answered them..
		if(time_since_last_qry < 1000) {
			return p.peerList;
		}
		// Query own peers
		ArrayList<PeerData> rpeers;
		
		for (PeerData r : p.peerList) {
			// Query the peer
			remote_api = XmlRpc.createClient(RPCApi.class, "Peer", r.address, r.port);
			if(r.name.equals(name))
				continue;
			rpeers = remote_api.query(ttl, name);
			// Add his peers
			for (PeerData rp : rpeers) {
				if (!resultList.contains(rp))
					resultList.add(rp);
			}
		}

		// Add local peer data if not already in list.
		if (!resultList.contains(p.localPeerData))
			resultList.add(p.localPeerData);

		return resultList;
	}

	@Override
	public boolean greet(PeerData rPD) {
		if(p.neighbours.size() < p.localPeerData.capacity && !p.neighbours.contains(rPD)){
			p.neighbours.add(rPD);
			return true;
		}
		return false;
	}
	
	// decrease ttl and forward to all your neighbours
	// unless: you answered recently or ttl == 0
	public String find(String key, String name, int ttl) {
		++p.packet_received;
		String ret = "";
		// t = ms since last search from this Peer for this key
		Long t = last_cmd(name, "find" + key);
		register_cmd(name, "find" + key);
		if(ttl > 0 && t > 500) {
			--ttl;
			if(p.resources.containsKey(key)) {
				ret = p.resources.get(key);
			} else {
				RPCApi remote_api;
				for(PeerData pd : p.neighbours) {
					String r = "";
					remote_api = XmlRpc.createClient(RPCApi.class, "Peer", pd.address, pd.port);
					r = remote_api.find(key, p.localPeerData.name, ttl);
					if(r.length() > 0) {
						ret = r;
						break;
					}
				}
			}
		}
		++p.packet_sent; // increments cause we sent an answer
		return ret;
	}
	
	// same as find, we just query randomly few of our neighbours
	public String walk(String key, String name, int ttl) {
		++p.packet_received;
		String ret = "";
		// t = time since last walk-search from this peer for this key
		Long t = last_cmd(name, "wfind" + key);
		register_cmd(name, "wfind" + key);
		if(ttl > 0 && t > 1000) {
			// Pretty print the walking process...
			//for(int i = 10; i > ttl; --i)
			//    System.out.print(" ");
			//System.out.print(p.localPeerData.name + ": walk(key:" + key + ", ttl:" + ttl + ") from " + name + "\n");
			if(p.resources.containsKey(key)) {
				ret = p.resources.get(key);
			} else {
				RPCApi remote_api;
				for(PeerData pd : p.neighbours) {
					String r = "";
					Random rand = new Random();
					int min = 0, max = 100;
					int randomNum = rand.nextInt(max - min + 1) + min;
					if(randomNum > 20)
						continue;
					remote_api = XmlRpc.createClient(RPCApi.class, "Peer", pd.address, pd.port);
					r = remote_api.walk(key, p.localPeerData.name, ttl - 1);
					if(r.length() > 0) {
						ret = r;
						break;
					}
				}
			}
		}
		++p.packet_sent; // increments cause we sent an answer
		return ret;
	}

}